#include "device.h"
#include "spdlog/spdlog.h"
#include "pugixml.hpp"
#include "gb28181_header_maker.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <string>
#include <sstream>
#include <thread>
#include <tuple> 

static int SN_MAX = 99999999; //最大心跳计数
static int sn;

//心跳计数累加
static int get_sn() 
{
	if (sn >= SN_MAX) 
    {
		sn = 0;
	}
	sn++;
	return sn;
}
// 打包成ps流，rtp推送
void Device::push_rtp_stream() 
{
    is_pushing = true;

    auto status = this->socket_bind();//绑定socket地址
    if (status != 0) 
    {
        spdlog::error("device bind socket address failed: {}", status);
        return ;
    }

	char ps_header[PS_HDR_LEN];

	char ps_system_header[SYS_HDR_LEN];

	char ps_map_header[PSM_HDR_LEN];

	char pes_header[PES_HDR_LEN];

	char rtp_header[RTP_HDR_LEN];    

	int time_base = 90000;
	int fps = 24;
	int send_packet_interval = 1000 / fps;

	int interval = time_base / fps;
	long pts = 0;

	char frame[1024 * 128];

	int single_packet_max_length = 1400; //rtp传输时的最大包长

	char rtp_packet[RTP_HDR_LEN+1400];

	// int ssrc = 0xffffffff;
	int rtp_seq = 0;

    // Nalu *nalu = new Nalu();
	// nalu->packet = (char *)malloc(1024*128);
	// nalu->length = 1024 * 128;
    //开始推流
    while (is_pushing) 
    {
        for (auto i = 0; i < nalu_vector.size(); i++) 
        {
            auto nalu = nalu_vector.at(i);

            NaluType  type = nalu->type;
            int length = nalu->length;
            char * packet = nalu->packet;

            int index = 0;

            //I帧：RTP header|PSH|SYS|PSM|PES头部|SPS nalu|PES头部|PPS nalu|PES头部|I帧
            //其余：RTP header|I帧剩余部分
            if (NALU_TYPE_IDR == type)  //IDR帧,ps+sys+psm
            {
                gb28181_make_ps_header(ps_header, pts); //封装ps头

                memcpy(frame,ps_header,PS_HDR_LEN);
                index += PS_HDR_LEN;

                gb28181_make_sys_header(ps_system_header, 0x3f); //封装sys头

                memcpy(frame+ index, ps_system_header, SYS_HDR_LEN);
                index += SYS_HDR_LEN;

                gb28181_make_psm_header(ps_map_header); //封装psm头

                memcpy(frame + index, ps_map_header, PSM_HDR_LEN);
                index += PSM_HDR_LEN;

            } 
            //P/B帧：RTP header|PSH|PES头部|P/B帧
            //其余：RTP header|P/B帧剩余部分
            else 
            {
                gb28181_make_ps_header(ps_header, pts);

                memcpy(frame, ps_header, PS_HDR_LEN);
                index += PS_HDR_LEN;
            }

            //封装pes
            gb28181_make_pes_header(pes_header, 0xe0, length, pts, pts);

            memcpy(frame+index, pes_header, PES_HDR_LEN);
            index += PES_HDR_LEN;

            //数据
            memcpy(frame + index, packet, length);
            index += length;

            //组rtp包
            int rtp_packet_count = ((index - 1) / single_packet_max_length) + 1;

            for (int i = 0; i < rtp_packet_count; i++) 
            {
                // 封装RTP头
                gb28181_make_rtp_header(rtp_header, rtp_seq, pts, atoi(ssrc.c_str()), i == (rtp_packet_count - 1));

                int writed_count = single_packet_max_length;

                if ((i + 1)*single_packet_max_length > index) 
                {
                    writed_count = index - (i* single_packet_max_length);
                }
                //添加包长字节
                int rtp_start_index=0;

                unsigned short rtp_packet_length = RTP_HDR_LEN + writed_count;
                if (rtp_protocol == "TCP/RTP/AVP") //添加2字节长度
                {
                    unsigned char packt_length_ary[2];
                    packt_length_ary[0] = (rtp_packet_length >> 8) & 0xff;
                    packt_length_ary[1] = rtp_packet_length & 0xff;
                    memcpy(rtp_packet, packt_length_ary, 2);
                    rtp_start_index = 2;
                }

                memcpy(rtp_packet+rtp_start_index, rtp_header, RTP_HDR_LEN);
                memcpy(rtp_packet+rtp_start_index + RTP_HDR_LEN, frame+ (i* single_packet_max_length), writed_count);
                rtp_seq++;

                if (is_pushing) 
                {
                    //发送rtp包
                    send_network_packet(rtp_packet, rtp_start_index + rtp_packet_length);
                }
                else 
                {
                    if (nalu != nullptr) 
                    {
                        delete nalu;
                        nalu = nullptr;
                    }
                    return;
                }
            }

            pts += interval;

            //帧间隔时长:1/fps
            std::this_thread::sleep_for(std::chrono::milliseconds(send_packet_interval));
        }
    }

    is_pushing = false;
}
//启动sip
void Device::start() 
{
    spdlog::info("sip init begin.");

    //初始化eXosip and osip堆栈
    sip_context = eXosip_malloc(); 

    if (OSIP_SUCCESS != eXosip_init(sip_context)) 
    {
        spdlog::error("sip init failed.");
        return;
    }
    //打开UDP socket
    if (OSIP_SUCCESS != eXosip_listen_addr(sip_context, IPPROTO_UDP, nullptr, local_port, AF_INET, 0)) 
    {
        spdlog::critical("sip port bind failed.");
        eXosip_quit(sip_context); //释放掉资源退出
        sip_context = nullptr;
        return;
    }

    // run
    is_running = true;

    ostringstream from_uri;
    ostringstream contact;
    ostringstream proxy_uri;

    // local ip & port
    //查找本机IP
    eXosip_guess_localip(sip_context, AF_INET, data(local_ip), local_ip.length());
    spdlog::info("local ip is {}", local_ip);

    from_uri << "sip:" << device_sip_id << "@" << local_ip << ":" << local_port;
    contact << "sip:" << device_sip_id << "@" << local_ip << ":" << local_port;
    proxy_uri << "sip:" << server_sip_id << "@" << server_ip << ":" << server_port;

    from_sip = from_uri.str();
    to_sip = proxy_uri.str();

    spdlog::info("from uri is {}", from_sip);
    spdlog::info("contact is {}", contact.str());
    spdlog::info("proxy_uri is {}", to_sip);

    // clear auth
    //清除存储在eXosip中的所有身份验证凭证
    eXosip_clear_authentication_info(sip_context);

    osip_message_t * register_message = nullptr;
    //构建初始的注册请求，获取注册ID和注册信息
    int register_id = eXosip_register_build_initial_register(sip_context, from_sip.c_str(), 
                    to_sip.c_str(), 
                    contact.str().c_str(), 3600, &register_message);
    if (nullptr == register_message) 
    {
        spdlog::error("eXosip_register_build_initial_register failed");
        return;
    }

    //锁定 sip_context
    eXosip_lock(sip_context);
    //为现有的注册发送REGISTER请求
	eXosip_register_send_register(sip_context, register_id, register_message);
    //sip_context 解锁
	eXosip_unlock(sip_context);
    //心跳线程
    thread heartbeat_task_thread(&Device::heartbeat_task, this);
    heartbeat_task_thread.detach(); //线程与主线程分离，在后台运行
    
    //处理请求/响应
    this->process_request();
}

//处理请求/响应
void Device::process_request() 
{
    while (is_running) 
    {
        //等待eXosip事件，100ms超时
        auto evt = shared_ptr<eXosip_event_t>(
            eXosip_event_wait(sip_context, 0, 100),
            eXosip_event_free);

        eXosip_lock(sip_context);
        //自动处理一些事件，如401/407/422
        eXosip_automatic_action(sip_context);
        eXosip_unlock(sip_context);

        if (evt == nullptr) 
        {
            continue;
        }

        switch (evt->type)
        {
            case eXosip_event_type::EXOSIP_REGISTRATION_SUCCESS: //注册成功
            {
                spdlog::info("got REGISTRATION_SUCCESS");
                is_register = true;
                break;
            }
            case eXosip_event_type::EXOSIP_REGISTRATION_FAILURE: //注册失败 
            {
                spdlog::info("got REGISTRATION_FAILURE");
                if (evt->response == nullptr) 
                {
                    spdlog::error("register 401 has no response !!!");
                    break;
                }
                //status_code代表的含义：https://www.zhihu.com/tardis/bd/art/270297661
                if (401 == evt->response->status_code) //401：未经授权
                {
                    osip_www_authenticate_t* www_authenticate_header; //验证头结构体

                    //根据response,获取验证头
                    osip_message_get_www_authenticate(evt->response, 0, &www_authenticate_header);

                    //添加身份验证凭证
                    if (eXosip_add_authentication_info(sip_context, device_sip_id.c_str(), username.c_str(), password.c_str(), 
                                        "MD5", www_authenticate_header->realm)) 
                    {
                        spdlog::error("register add auth failed");
                        break;
                    };
                };
                break;
            }
            case eXosip_event_type::EXOSIP_MESSAGE_NEW: //消息请求
            {
                spdlog::info("got MESSAGE_NEW");

                if (MSG_IS_MESSAGE(evt->request)) //测试是否是MESSAGE REQUEST
                {
                    osip_body_t * body = nullptr;
                    osip_message_get_body(evt->request, 0, &body); //获取请求体
                    if (body != nullptr) 
                    {
                        spdlog::info("new message request: \n{}", body->body);
                    }

                    this->send_response_ok(evt);//发送成功应答200(OK)

                    auto cmd_sn = this->get_cmd(body->body); //获取请求类型
                    string cmd = get<0>(cmd_sn);
                    string sn = get<1>(cmd_sn);
                    spdlog::info("got new cmd: {}", cmd);
                    if ("Catalog" == cmd) 
                    {
                        this->process_catalog_query(sn);//查询设备网络信息
                    } 
                    else if ("DeviceStatus" == cmd) 
                    {
                        this->process_devicestatus_query(sn); //查询设备状态，其实回的是设备时间
                    } 
                    else if ("DeviceInfo" == cmd) 
                    {
                        this->process_deviceinfo_query(sn);//查询设备固件信息
                    } 
                    else if ("DeviceControl" == cmd)
                    {
                        this->process_devicecontrol_query(sn);//设备控制，空
                    } 
                    else 
                    {
                        spdlog::error("unhandled cmd: {}", cmd);
                    }
                } 
                else if (MSG_IS_BYE(evt->request)) //BYE REQUEST
                {
                    spdlog::info("got BYE message");
                    this->send_response_ok(evt); //回成功应答
                    break;
                }
                break;
            }
            case eXosip_event_type::EXOSIP_CALL_INVITE: //呼叫请求
            {
                spdlog::info("got CALL_INVITE");

                // 为呼叫的最新邀请得到远程SDP体
                auto sdp_msg = eXosip_get_remote_sdp(sip_context, evt->did);
                if (!sdp_msg) 
                {
                    spdlog::error("eXosip_get_remote_sdp failed");
                    break;
                }
                //获取通话视频连接信息
                auto connection = eXosip_get_video_connection(sdp_msg);
                if (!connection) 
                {
                    spdlog::error("eXosip_get_video_connection failed");
                    break;                
                }

                rtp_ip = connection->c_addr;

                //获取视频媒体信息
                auto video_sdp = eXosip_get_video_media(sdp_msg);
                if (!video_sdp) 
                {
                    spdlog::error("eXosip_get_video_media failed");
                    break;                  
                }

                rtp_port = atoi(video_sdp->m_port);//端口

                spdlog::info("rtp server: {}:{}", rtp_ip, rtp_port);

                rtp_protocol = video_sdp->m_proto; //协议

                spdlog::info("rtp protocol: {}", rtp_protocol);

                osip_body_t *sdp_body = NULL;
                //获取请求体
                osip_message_get_body(evt->request, 0, &sdp_body);
                if (nullptr == sdp_body) 
                {
                    spdlog::error("osip_message_get_body failed");
                    break; 
                }
                // 解析sdp
                string body = sdp_body->body;
                auto y_sdp_first_index = body.find("y=");
                auto y_sdp = body.substr(y_sdp_first_index);
                auto y_sdp_last_index = y_sdp.find("\r\n");
                ssrc = y_sdp.substr(2, y_sdp_last_index-1);
                spdlog::info("ssrc: {}", ssrc);

                stringstream ss;
                ss << "v=0\r\n";
                ss << "o=" << device_sip_id << " 0 0 IN IP4 " << local_ip << "\r\n";
                ss << "s=Play\r\n";
                ss << "c=IN IP4 " << local_ip << "\r\n";
                ss << "t=0 0\r\n";
                if (rtp_protocol == "TCP/RTP/AVP") 
                {
                    ss << "m=video " << local_port+1 << " TCP/RTP/AVP 96\r\n";
                }
                else 
                {
                    ss << "m=video " << local_port+1 << " RTP/AVP 96\r\n";
                }
                ss << "a=sendonly\r\n";
                ss << "a=rtpmap:96 PS/90000\r\n";
                ss << "y=" << ssrc << "\r\n";
                string sdp_output_str  = ss.str();

                size_t size = sdp_output_str.size();

                osip_message_t * message = evt->request;
                //构建一个请求回答
                int status = eXosip_call_build_answer(sip_context, evt->tid, 200, &message);

                if (status != 0) 
                {
                    spdlog::error("call invite build answer failed");
                    break;
                }
                //设置消息头
                osip_message_set_content_type(message, "APPLICATION/SDP");
                //设置消息体
                osip_message_set_body(message, sdp_output_str.c_str(), sdp_output_str.size());
                //发送呼叫回复
                eXosip_call_send_answer(sip_context, evt->tid, 200, message);

                spdlog::info("reply call invite: \n{}", sdp_output_str);
                break;
            }
            case eXosip_event_type::EXOSIP_CALL_ACK:  //呼叫应答,开启rtp推流
            {
                spdlog::info("got CALL_ACK: begin pushing rtp stream...");

                if (is_pushing) 
                {
                    spdlog::info("already pushing rtp stream");
                } 
                else 
                {
                    thread t(&Device::push_rtp_stream, this);//推送rtp流线程
                    t.detach();
                }

                break;
            }
            case eXosip_event_type::EXOSIP_CALL_CLOSED: //呼叫关闭
            {
                spdlog::info("got CALL_CLOSED: stop pushing rtp stream...");

                break;
            }
            case eXosip_event_type::EXOSIP_MESSAGE_ANSWERED: //成功应答
            {
                spdlog::info("got MESSAGE_ANSWERED: unhandled");
                 this->send_response_ok(evt);//发送成功应答200(OK)
                break;
            }
            case eXosip_event_type::EXOSIP_IN_SUBSCRIPTION_NEW: 
            {
                spdlog::info("got EXOSIP_IN_SUBSCRIPTION_NEW: unhandled");
                 this->send_response_ok(evt);//发送成功应答200(OK)
                break;
            }
            
            default: //其他请求类型
            {
                spdlog::info("unhandled sip evt type: {}", evt->type);
                break;
            }
        }
    }
}

//查询设备网络信息应答
void Device::process_catalog_query(string sn) 
{
    stringstream ss;
    ss << "<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n";
    ss << "<Response>\r\n";
    ss << "<CmdType>Catalog</CmdType>\r\n";
    ss << "<SN>" << sn << "</SN>\r\n";
    ss << "<DeviceID>" << device_sip_id << "</DeviceID>\r\n";
    ss << "<SumNum>" << 1 << "</SumNum>\r\n";
    ss << "<DeviceList Num=\"" << 1 << "\">\r\n";
    ss << "<Item>\r\n";
    ss << "<DeviceID>" << device_sip_id << "</DeviceID>\r\n";
    ss << "<Manufacturer>" << manufacture << "</Manufacturer>\r\n";
    ss << "<Status>ON</Status>\r\n";
    ss << "<Name>IPC</Name>\r\n";
    ss << "<ParentID>" << server_sip_id << "</ParentID>\r\n";
    ss << "</Item>\r\n";
    ss << "</DeviceList>\r\n";
    ss << "</Response>\r\n";
    spdlog::info("catalog response: \n{}", ss.str());
    auto request = create_msg();//构建个"MESSAGE"消息
    if (request != NULL) 
    {
        //设置消息头
        osip_message_set_content_type(request, "Application/MANSCDP+xml");
        //设置消息体
        osip_message_set_body(request, ss.str().c_str(), strlen(ss.str().c_str()));
        //发送消息
        send_request(request);
    }
}
//获取设备时间应答
void Device::process_devicestatus_query(string sn) 
{
    stringstream ss;

    time_t rawtime;
    struct tm* timeinfo;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    char curtime[72] = {0};
    sprintf(curtime, "%d-%d-%dT%02d:%02d:%02d", (timeinfo->tm_year + 1900), (timeinfo->tm_mon + 1),
                        timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);    
    
    ss << "<?xml version=\"1.0\"?>\r\n";
    ss << "<Response>\r\n";
    ss << "<CmdType>DeviceStatus</CmdType>\r\n";
    ss << "<SN>" << get_sn() << "</SN>\r\n";
    ss << "<DeviceID>" << device_sip_id << "</DeviceID>\r\n";
    ss << "<Result>OK</Result>\r\n";
    ss << "<Online>ONLINE</Online>\r\n";
    ss << "<Status>OK</Status>\r\n";
    ss << "<DeviceTime>" << curtime << "</DeviceTime>\r\n";
    ss << "<Alarmstatus Num=\"0\">\r\n";
    ss << "</Alarmstatus>\r\n";
    ss << "<Encode>ON</Encode>\r\n";
    ss << "<Record>OFF</Record>\r\n";
    ss << "</Response>\r\n";

    spdlog::info("devicestatus response: \n{}", ss.str());
    auto request = create_msg();
    if (request != NULL) 
    {
        osip_message_set_content_type(request, "Application/MANSCDP+xml");
        osip_message_set_body(request, ss.str().c_str(), strlen(ss.str().c_str()));
        send_request(request);
    }
}

//获取设备的固件信息应答
void Device::process_deviceinfo_query(string sn) {
    stringstream ss;

    ss << "<?xml version=\"1.0\"?>\r\n";
    ss <<    "<Response>\r\n";
    ss <<    "<CmdType>DeviceInfo</CmdType>\r\n";
    ss <<    "<SN>" << get_sn() << "</SN>\r\n";
    ss <<    "<DeviceID>" << device_sip_id << "</DeviceID>\r\n";
    ss <<    "<Result>OK</Result>\r\n";
    ss <<    "<DeviceType>simulate client</DeviceType>\r\n";
    ss <<    "<Manufacturer>ZHD</Manufacturer>\r\n";
    ss <<    "<Model>28181</Model>\r\n";
    ss <<    "<Firmware>fireware</Firmware>\r\n";
    ss <<    "<MaxCamera>1</MaxCamera>\r\n";
    ss <<    "<MaxAlarm>0</MaxAlarm>\r\n";
    ss <<    "</Response>\r\n";

    spdlog::info("deviceinfo response: \n{}", ss.str());
    auto request = create_msg();
    if (request != NULL) {
        osip_message_set_content_type(request, "Application/MANSCDP+xml");
        osip_message_set_body(request, ss.str().c_str(), strlen(ss.str().c_str()));
        send_request(request);
    }
}

void Device::process_devicecontrol_query(string sn) 
{

}
//发送心跳
void Device::heartbeat_task() 
{
	while (true) 
    {
        if (is_register) 
        {
            stringstream ss;
            ss << "<?xml version=\"1.0\"?>\r\n";
            ss << "<Notify>\r\n";
            ss << "<CmdType>Keepalive</CmdType>\r\n";
            ss << "<SN>" << get_sn() << "</SN>\r\n";  //心跳数+1
            ss << "<DeviceID>" << device_sip_id << "</DeviceID>\r\n";
            ss << "<Status>OK</Status>\r\n";
            ss << "</Notify>\r\n";

            osip_message_t* request = create_msg(); //创建个MESSAGE
            if (request != NULL) 
            {
                //设置SIP请求头
                osip_message_set_content_type(request, "Application/MANSCDP+xml");
                //设置SIP请求体
                osip_message_set_body(request, ss.str().c_str(), strlen(ss.str().c_str()));
                //发送请求
                send_request(request);
                spdlog::info("sent heartbeat");
            }
        }
        //60s发一次心跳
		std::this_thread::sleep_for(std::chrono::seconds(60));
	}
}
//创建个信息
osip_message_t * Device::create_msg() 
{

    osip_message_t * request = nullptr;
    //构建个"MESSAGE" 请求
    auto status = eXosip_message_build_request(sip_context, &request, "MESSAGE", to_sip.c_str(), from_sip.c_str(), nullptr);
    if (OSIP_SUCCESS != status) 
    {
        spdlog::error("build request failed: {}", status);
    }

    return request;
}

void Device::send_request(osip_message_t * request) {
    eXosip_lock(sip_context);
    //发送请求
    eXosip_message_send_request(sip_context, request);
    eXosip_unlock(sip_context);
}

void Device::send_response(shared_ptr<eXosip_event_t> evt, osip_message_t * msg) {
    eXosip_lock(sip_context);
    //发送成功应答
    eXosip_message_send_answer(sip_context, evt->tid, 200, msg);
    eXosip_unlock(sip_context);
}

void Device::send_response_ok(shared_ptr<eXosip_event_t> evt) {
    auto msg = evt->request;
    //创建一个请求成功应答
    eXosip_message_build_answer(sip_context, evt->tid, 200, &msg);
    //发送成功应答
    send_response(evt, msg);
}

//解析xml，获取指令
std::tuple<string, string> Device::get_cmd(const char * body) 
{
    pugi::xml_document document;

    if (!document.load(body)) 
    {
        spdlog::error("cannot parse the xml");
        return make_tuple("", "");
    }

    pugi::xml_node root_node = document.first_child();

    if (!root_node) 
    {
        spdlog::error("cannot get root node of xml");
        return make_tuple("", "");
    }

    string root_name = root_node.name();
    if ("Query" != root_name) 
    {
        spdlog::error("invalid query xml with root: {}", root_name);
        return make_tuple("", "");
    }

    auto cmd_node = root_node.child("CmdType");

    if (!cmd_node)
     {
        spdlog::error("cannot get the cmd type");
        return make_tuple("", "");
    }

    auto sn_node = root_node.child("SN");

    if (!sn_node) 
    {
        spdlog::error("cannot get the SN");
        return make_tuple("", "");
    }

    string cmd = cmd_node.child_value(); //命令类型
    string sn = sn_node.child_value(); //心跳号

    return make_tuple(cmd, sn);
}